<?php //-->
/*
 * This file is part of the Eve package.
 * (c) 2011-2012 Openovate Labs
 *
 * Copyright and license information can be found at LICENSE.txt
 * distributed with this package.
 */

/**
 * Category Model
 *
 * @package    Eve
 * @category   module
 * @author     Christian Blanquera cblanquera@openovate.com
 */
class Category_Model extends Eden_Sql_Model {
	/* Constants
	-------------------------------*/
	/* Public Properties
	-------------------------------*/
	/* Protected Properties
	-------------------------------*/
	protected $_relations 	= array();
	
	/* Private Properties
	-------------------------------*/
	/* Magic
	-------------------------------*/
	public static function i() {
		return self::_getMultiple(__CLASS__);
	}
	
	public function __construct($data = array()) {
		if(!isset($data['category_parent'])) {
			$data['category_parent'] = 0;
		}
		
		if(!isset($data['category_active'])) {
			$data['category_active'] = 1;
		}
		
		$this->setTable('category');
		
		parent::__construct($data);
	}
	
	/* Public Methods
	-------------------------------*/
	/**
	 * Relates file to category
	 *
	 * @param int|array
	 * @return this
	 */
	public function addFile($file) {
		Category_Error::i()->argument(1, 'int', 'array');
		
		if(!is_array($file)) {
			$file = func_get_args();
		}
		
		foreach($file as $relation) {
			$this->addRelation('file', $relation);
		}
		
		return $this;
	}
	
	/**
	 * Relates group to category
	 *
	 * @param string|int|array
	 * @return this
	 */
	public function addGroup($group) {
		Category_Error::i()->argument(1, 'string', 'int', 'array');
		
		if(!is_array($group)) {
			$group = func_get_args();
		}
		
		foreach($group as $relation) {
			if(!is_numeric($relation)) {
				$row = $this->_database->getRow('group', 'group_slug', $relation);
				$relation = $row['group_id'];
			}
		
			$this->addRelation('group', $relation);
		}
		
		return $this;
	}
	
	/**
	 * Relates post to category
	 *
	 * @param string|int|array
	 * @return this
	 */
	public function addPost($post) {
		Category_Error::i()->argument(1, 'string', 'int', 'array');
		
		if(!is_array($post)) {
			$post = func_get_args();
		}
		foreach($post as $relation) {
			if(!is_numeric($relation)) {
				$row = $this->_database->getRow('post', 'post_slug', $relation);
				$relation = $row['post_id'];
			}
			
			$this->addRelation('post', $relation);
		}
		
		return $this;
	}
	
	/**
	 * Adds relation with post
	 *
	 * @param string|int
	 * @return this
	 */
	public function addRelation($table, $id) {
		Category_Error::i()
			->argument(1, 'string')
			->argument(2, 'string', 'int');
			
		$this->_relations[] = array($table, $id, true);
		return $this;
	}
	
	/**
	 * Relates user to category
	 *
	 * @param string|int|array
	 * @return this
	 */
	public function addUser($user) {
		Category_Error::i()->argument(1, 'string', 'int', 'array');
		
		if(!is_array($group)) {
			$file = func_get_args();
		}
		
		foreach($user as $relation) {
			if(!is_numeric($relation)) {
				$row = $this->_database->getRow('user', 'user_slug', $relation);
				$relation = $row['user_id'];
			}
		
			$this->addRelation('user', $relation);
		}
		
		return $this;
	}
	
	/**
	 * Returns the children of the category
	 *
	 * @param string|null category type
	 * @param bool recursive
	 * @param array sort
	 * @return array
	 */
	public function getChildren($type = NULL, $recursive = false, array $sort = array()) {
		Category_Error::i()
			->argument(1, 'string', 'null')
			->argument(2, 'bool');
			
		if(!isset($this->_data['category_id'])) {
			return array();
		}
		
		$join = $filter = array();
		
		if(!is_null($type)) {
			$filter[] = array('category_type=%s', $type);
		}
		
		if(!$recursive) {
			$filter[] = array('category_parent=%s', $this->_data['category_id']);
		} else {
			$row 	= $this->getRow($this->_data['category_id']);
			$left 	= $row['category_left'] + 1;
			$right 	= $row['category_right'] - 1;
			$filter[] = array('category_left BETWEEN %s AND %s', $left, $right);
			$sort['category_left'] = 'ASC';
		}
		
		return $this->_database->getRows('category', $join, $filter, $sort);
	}
	
	/**
	 * Returns all the parents for this category
	 *
	 * @param int category id
	 * @return array
	 */
	public function getParents() {
		if(!isset($this->_data['category_id'])) {
			return array();
		}
		
		$join = array();
		$row 	= $this->getRow($this->_data['category_id']);
		$filter = $row['category_left'].' BETWEEN category_left AND category_right';
		$sort['category_left'] = 'ASC';
		
		return $this->_database->getRows('category', $join, $filter, $sort);
	}
	
	/**
	 * Returns posts related to category
	 *
	 * @param string|null|bool
	 * @return array
	 */
	public function getPosts($type = false) {
		Category_Error::i()->argument(1, 'string','null','bool');
		return $this->searchPosts($type)->getRows();
	}
	
	/**
	 * Returns the search class set to relations and post
	 *
	 * @param string
	 * @return Eden_Sql_Search
	 */
	public function getRelations($table, $type = false) {
		Category_Error::i()
			->argument(1, 'string')
			->argument(2, 'string','null','bool');
			
		return $this->searchRelation($table, $type)->getRows();
	}
	
	/**
	 * Returns the total amount of children
	 *
	 * @param int category id
	 * @param string|null category type
	 * @param bool recursive
	 * @return int
	 */
	public function getTotalChildren($type = NULL, $recursive = false) {
		Category_Error::i()
			->argument(1, 'string', 'null')
			->argument(2, 'bool');
			
		if(!isset($this->_data['category_id'])) {
			return 0;
		}
		
		$join = $filter = array();
		
		if(!is_null($type)) {
			$filter[] = array('category_type=%s', $type);
		}
		
		if(!$recursive) {
			$filter[] = array('category_parent=%s', $this->_data['category_id']);
		} else {
			$row 	= $this->getRow($this->_data['category_id']);
			$left 	= $row['category_left'] + 1;
			$right 	= $row['category_right'] - 1;
			$filter[] = array('category_left BETWEEN %s AND %s', $left, $right);
			$sort['category_left'] = 'ASC';
		}
		
		return $this->_database->getRows('category', $join, $filter);
	}
	
	/**
	 * Returns users related to category
	 *
	 * @param string|null|bool
	 * @return array
	 */
	public function getUsers($type = false) {
		Category_Error::i()->argument(1, 'string','null','bool');
		return $this->searchUsers($type)->getRows();
	}
	
	/**
	 * Inserts model to database
	 *
	 * @param string
	 * @param Eden_Sql
	 * @return this
	 */
	public function insert($table = NULL, Eden_Sql_Database $database = NULL) {
		Category_Error::i()->argument(1, 'string', 'null');
		
		//if title is set
		if(isset($this->_data['category_title'])) {
			$this->_data['category_slug'] = $this->_getSlug($this->_data['category_title']);
		}
		
		$this->_data['category_created'] = date('Y-m-d H:i:s');
		$this->_data['category_updated'] = date('Y-m-d H:i:s');
		
		parent::insert($table, $database);
		
		$this->_setRelations()->_addLeftRight($this->_data['category_id'], $this->_data['category_parent']);
		
		return $this;
	}
	
	/**
	 * Loads a category into the model
	 *
	 * @param mixed
	 * @param string
	 * @return this
	 */
	public function load($value, $key = 'category_id') {
		Category_Error::i()
			->argument(1, 'scalar')
			->argument(2, 'string');
			
		$row = $this->_database->getRow('category', $key, $value);
		parent::__construct($row);
		return $this;
	}

	/**
	 * Removes relation from post to category
	 *
	 * @param string|int|array
	 * @return this
	 */
	public function removePost($post) {
		Category_Error::i()->argument(1, 'string', 'int', 'array');
		
		if(!is_array($post)) {
			$post = func_get_args();
		}
		
		foreach($post as $relation) {
		
			if(is_string($relation)) {
				$row = $this->_database->getRow('post', 'post_slug', $relation);
				$relation = $row['post_id'];
			}
		
			$this->removeRelation('post', $relation);
		}
		
		return $this;
	}
	
	/**
	 * Removes relation with post
	 *
	 * @param string|int
	 * @return this
	 */
	public function removeRelation($table, $id) {
		Category_Error::i()
			->argument(1, 'string')
			->argument(2, 'string', 'int');
			
		$this->_relations[] = array($table, $id, false);
		return $this;
	}
	
	/**
	 * Removes relation from user to category
	 *
	 * @param string|int|array
	 * @return this
	 */
	public function removeUser($user) {
		Category_Error::i()->argument(1, 'string', 'int', 'array');
		
		if(!is_array($group)) {
			$file = func_get_args();
		}
		
		foreach($user as $relation) {
		
			if(is_string($relation)) {
				$row = $this->_database->getRow('user', 'user_slug', $relation);
				$relation = $row['user_id'];
			}
		
			$this->removeRelation('user', $relation);
		}
		
		return $this;
	}
	
	/**
	 * Returns the search class set to post and category
	 *
	 * @param string|null|bool
	 * @return Eden_Sql_Search
	 */
	public function searchPosts($type = false) {
		$error = Category_Error::i()->argument(1, 'string','null','bool');
		
		if(!isset($this->_data['category_id'])) {
			$error->setMessage(Relation_Error::NO_PRIMARY_SEARCH)->trigger();
		}
		
		return $this->searchRelation('post', $type)->setModel('Post_Model');
	}
	
	/**
	 * Returns the search class set to relations and post
	 *
	 * @param string
	 * @return Eden_Sql_Search
	 */
	public function searchRelation($table, $type = false) {
		$error = Category_Error::i()
			->argument(1, 'string')
			->argument(2, 'string','null','bool');
		
		if(!isset($this->_data['category_id'])) {
			$error->setMessage(Relation_Error::NO_PRIMARY_SEARCH)->trigger();
		}
		
		return $this->Relation()->search('category', $this->_data['category_id'], $table, $type);
	}
	
	/**
	 * Returns the search class set to user and category
	 *
	 * @param string|null|bool
	 * @return Eden_Sql_Search
	 */
	public function searchUsers($type = false) {
		$error = Category_Error::i()->argument(1, 'string','null','bool');
		
		if(!isset($this->_data['category_id'])) {
			$error->setMessage(Relation_Error::NO_PRIMARY_SEARCH)->trigger();
		}
		
		return $this->searchRelation('user', $type)->setModel('User_Model');
	}
	
	/**
	 * Updates model to database
	 *
	 * @param string
	 * @param Eden_Sql_Database
	 * @param string|null|array
	 * @return this
	 */
	public function update($table = NULL, Eden_Sql_Database $database = NULL, $primary = NULL) {
		Category_Error::i()
			->argument(1, 'string', 'null')
			->argument(3, 'string', 'null', 'array');
			
		$row = $this->_database->getRow('category', 'category_id', $this->_data['category_id']);
		
		if($this->_data['category_title'] != $row['category_title']) {
			$this->_data['category_slug'] = $this->_getSlug($this->_data['category_title']);
		}
		
		$this->_data['category_updated'] = date('Y-m-d H:i:s');
		
		if($this->_data['category_parent'] != $row['category_parent']) {
			$this->_updateLeftRight($categoryId, $this->_data['category_parent']);
		}
		
		parent::update($table, $database, $primary);
		$this->_setRelations();
		
		return $this;
	}
	
	/* Protected Methods
	-------------------------------*/
	protected function _addLeftRight($categoryId, $parent = 0) {
		//see: http://www.phpro.org/tutorials/Managing-Hierarchical-Data-with-PHP-and-MySQL.html#9
		//if there is no parent
		if($parent == 0) {
			//we need to get the greatest node
			$join = $filter = $sort = array();
			$sort['category_right'] = 'DESC';
			$row = $this->_database->getRows('category', $join, $filter, $sort, 0, 1, 0);
			
			$filter = $settings = array();
			$settings['category_left'] = $row['category_right']+1;
			$settings['category_right'] = $row['category_right']+2;
			$filter[] = array('category_id=%s', $categoryId);
			$this->_database->updateRows('category', $settings, $filter);
			return $this;
		}
			
		//let's get the parent
		$row = $this->_database->getRow('category', 'category_id', $parent);
		
		$settings = array('category_right' => 'category_right+2');
		$filter = 'category_right > '.$row['category_left'];
		$this->_database->updateRows('category', $settings, $filter, false);
		
		$settings = array('category_left' => 'category_left+2');
		$filter = 'category_left > '.$row['category_left'];
		$this->_database->updateRows('category', $settings, $filter, false);
		
		$filter = $settings = array();
		$settings['category_left'] = $row['category_left']+1;
		$settings['category_right'] = $row['category_left']+2;
		$filter[] = array('category_id=%s', $categoryId);
		$this->_database->updateRows('category', $settings, $filter);
		return $this;
	}
	
	protected function _getSlug($title) {
		$slug = (string) eden('type', $title)->dasherize();
		$join = $filter = $sort = array();
		//SELECT * FROM `category` WHERE category_slug RLIKE '^test\-title\-[0-9]+$' ORDER BY category_slug DESC
		$filter[] = array('category_slug=%s OR category_slug RLIKE %s', $slug, 
					'^'.str_replace('-', '\\-', $slug).'-[0-9]+$');
					
		$sort = array('category_id' => 'DESC');
		$category = $this->_database->getRows('category', $join, $filter, $sort, 0, 1, 0);
		
		if(empty($category)) {
			return $slug;
		}
		
		$count = substr($category['category_slug'], strlen($slug)+1);
		$slug .= '-'.(is_numeric($count) ? $count+1 : 1);
		
		return $slug;
	}
	
	protected function _removeLeftRight($categoryId) {
		$row = $this->_database->getRow('category', 'category_id', $categoryId);
		$width = $row['category_right'] - $row['category_left'] + 1;
		
		$settings = array('category_right' => 'category_right - '.$width);
		$filter = 'category_right > '.$row['category_right'];
		$this->_database->updateRows('category', $settings, $filter, false);
		
		$settings = array('category_left' => 'left_node - '.$width);
		$filter = 'category_left > '.$row['category_right'];
		$this->_database->updateRows('category', $settings, $filter, false);
		
		$settings = array('category_left' => 0, 'category_right' => 0);
		$filter = 'category_left BETWEEN '.$row['category_left'].' AND '.$row['category_right'];
		$this->_database->updateRows('category', $settings, $filter, false);
		
		return $this;
	}
	
	protected function _setRelations() {
		//add categories first
		foreach($this->_relations as $relation) {
			if($relation[2]) {
				$this->Relation()->add('category', $this->_data['category_id'], $relation[0], $relation[1]);
				continue;
			}
			
			$this->Relation()->remove('category', $this->_data['category_id'], $relation[0], $relation[1]);
		}
		
		return $this;
	}
	
	protected function _updateLeftRight($categoryId, $parent) {
		//get the current row
		$row = $this->_database->getRow('category', 'category_id', $categoryId);
		$width = $row['category_right'] - $row['category_left'] + 1;
		
		//update the parent
		$settings = array('category_parent' => $parent);
		$filter = 'category_id ='.$row['category_id'];
		$this->_database->updateRows('category', $settings, $filter, false);
		
		//now get all the rows that need to change
		$join = $sort = array();
		$filter = 'category_left BETWEEN '.$row['category_left'].' AND '.$row['category_right'];
		$sort['category_left'] = 'ASC';
		$rows = $this->_database->getRows('category', $join, $filter);
		
		//update the right for the ones that is not the children
		$settings = array('category_right' => 'category_right - '.$width);
		$filter = 'category_right > '.$row['category_right'];
		$this->_database->updateRows('category', $settings, $filter, false);
		
		//update the left for the ones that is not the children
		$settings = array('category_left' => 'category_left - '.$width);
		$filter = 'category_left > '.$row['category_right'];
		$this->_database->updateRows('category', $settings, $filter, false);
		
		//now re add the nodes
		foreach($rows as $row) {
			$this->_addLeftRight($row['category_id'], $row['category_parent']);
		}
		
		return $this;
	}
	
	/* Private Methods
	-------------------------------*/
}